package x86;

import assem.Instr;
import assem.OPER;
import temp.Label;
import temp.Temp;
import tree.BINOP;
import tree.CALL;
import tree.CJUMP;
import tree.CONST;
import tree.ESEQ;
import tree.EXPSTM;
import tree.Exp;
import tree.JUMP;
import tree.LABEL;
import tree.MEM;
import tree.MOVE;
import tree.NAME;
import tree.PrintIR;
import tree.Stm;
import tree.TEMP;
import util.List;

public class Codegen
{
    Frame frame;
    public Codegen(Frame f)
    {
        frame=f;        
    }

    private List<Instr> ilist=null;
    private List<Instr> last=null;

    private void emit(Instr inst)
    {
        if (last!=null)
            last = last.tail = new List<Instr>(inst,null);
        else 
            last = ilist = new List<Instr>(inst,null);
    }

    private void munchStm (Stm s) {
        if( s instanceof MOVE)
            munchMove((MOVE)s);
        else if( s instanceof EXPSTM)
            munchExpStm((EXPSTM)s);
        else if( s instanceof CJUMP)
            munchCjump((CJUMP)s);
        else if( s instanceof LABEL)
            munchLabel((LABEL)s);
        else if( s instanceof JUMP)
            munchJump((JUMP)s);
        else
            throw new Error("Unhandled: "+ s.getClass());
    }

    private Temp munchExp(Exp e)
    {
        if (e instanceof BINOP)
            return munchBinop((BINOP)e);
        else if (e instanceof CALL)
            return munchCall((CALL)e);
        else if (e instanceof CONST)
            return munchConst((CONST)e);
        else if (e instanceof ESEQ)
            return munchEseq((ESEQ)e);
        else if (e instanceof MEM)
            return munchMem((MEM)e);
        else if (e instanceof NAME)
            return munchName((NAME)e);
        else if (e instanceof TEMP)
            return munchTemp((TEMP)e);
        else
            throw new Error("Unhandled: "+ e.getClass());
    }
    
    /*-------------------------------------------------------------*
     *                              MAIN                           *
     *-------------------------------------------------------------*/
    List<Instr> codegen(Stm s)
    {
        List<Instr> l;
        munchStm(s);
        l=ilist;
        ilist=last=null;
        return l;
    }
    
    List<Instr> codegen(List<Stm> body)
    {
        List<Instr> l = null, t = null;
        
        for( ; body != null; body = body.tail )
        {

//            PrintIR p = new PrintIR(System.out);
//            p.printStatement(body.head);
            munchStm(body.head);
            if ( l == null )
                l = ilist;
            else
                t.tail = ilist;
            t = last;
            ilist=last=null;
        }
        return l;
    }

    private void munchMove(MOVE s) {
        if(s.dst instanceof MEM)
            munchMove((MEM)s.dst,s.src);
        else
            munchMove((TEMP)s.dst,s.src);
    }

    private void munchMove(MEM d, Exp s) {
        Temp val = munchExp(s);
        Temp add = munchExp(d.exp);

        emit(new assem.OPER("mov [`s0], `s1",
                null,new List<Temp>(add,new List<Temp>(val,null))));
        return;
    }
    
    private void munchMove(TEMP d, Exp s) {
        Temp val = munchExp(s);
        Temp dest = d.temp;
        emit(new assem.MOVE("mov `d0, `s0",dest,val));
        return;
    }

    private void munchJump(JUMP s) {
        // tratando jump label
        if (s.exp instanceof NAME)
        {
            NAME l = (NAME) s.exp;
            emit(new assem.OPER("jmp `j0",null,null,new List<Label>(l.label,null)));
        }
        else
        {
            Temp target = munchExp(s.exp);
            emit(new assem.OPER("jmp `j0",null,new List<Temp>(target,null), s.targets));
        }
    }

    private void munchLabel(LABEL s) {
        emit(new assem.LABEL(s.label.toString()+":",s.label));
    }

    private void munchCjump(CJUMP s) {
        Temp val = munchExp(s.left);
        Temp add = munchExp(s.right);
        emit(new assem.OPER("cmp `s0, `s1",
                null,
                new List<Temp>(val,new List<Temp>(add,null))));
        switch(s.op){
            case CJUMP.EQ:
                emit(new assem.OPER("je  `j0",null,null,new List<Label>(s.ifTrue,new List<Label>(s.ifFalse,null))));
                break;
            case CJUMP.NE:
                emit(new assem.OPER("jne `j0",null,null,new List<Label>(s.ifTrue,new List<Label>(s.ifFalse,null))));
                break;
            case CJUMP.LT:
                emit(new assem.OPER("jl  `j0",null,null,new List<Label>(s.ifTrue,new List<Label>(s.ifFalse,null))));
                break;
            case CJUMP.LE:
                emit(new assem.OPER("jle `j0",null,null,new List<Label>(s.ifTrue,new List<Label>(s.ifFalse,null))));
                break;
            case CJUMP.GT:
                emit(new assem.OPER("jg  `j0",null,null,new List<Label>(s.ifTrue,new List<Label>(s.ifFalse,null))));
                break;
            case CJUMP.GE:
                emit(new assem.OPER("jge `j0",null,null,new List<Label>(s.ifTrue,new List<Label>(s.ifFalse,null))));
                break;
            case CJUMP.ULT:
                emit(new assem.OPER("jb  `j0",null,null,new List<Label>(s.ifTrue,new List<Label>(s.ifFalse,null))));
                break;
            case CJUMP.ULE:
                emit(new assem.OPER("jbe `j0",null,null,new List<Label>(s.ifTrue,new List<Label>(s.ifFalse,null))));
                break;
            case CJUMP.UGT:
                emit(new assem.OPER("ja  `j0",null,null,new List<Label>(s.ifTrue,new List<Label>(s.ifFalse,null))));
                break;
            case CJUMP.UGE:
                emit(new assem.OPER("jae `j0",null,null,new List<Label>(s.ifTrue,new List<Label>(s.ifFalse,null))));
                break;
        }
    }

    private void munchExpStm(EXPSTM s) {
        munchExp(s.exp);
    }

    private Temp munchBinop(BINOP s) {
                // Local Variables
                Temp temp = new Temp();

                switch (s.binop) {
                    case BINOP.LSHIFT:
                            temp =munchLSHIFT(s);
                            break;
                    case BINOP.MINUS:
                            temp =munchSUB(s);
                           break;
                    case BINOP.PLUS:
                            temp =munchADD(s);
                            break;
                   case BINOP.TIMES:
                            temp =munchMUL(s);
                            break;
                }
                return temp;
        }


    private Temp munchCall(CALL cALL) {
        List<Exp> arglist = null ;
        for(List<Exp> aux = cALL.args;aux!=null;aux=aux.tail)
        {
            if(arglist==null)
                arglist = new List<Exp>(aux.head,null);
            else
            {
                arglist.tail = new List<Exp>(arglist.head,arglist.tail);
                arglist.head = aux.head;
            }
        }
        List<Temp> regs = null;
        while(arglist!= null){
           Temp aux = munchExp(arglist.head);
            emit(new OPER("push `s0",new List<Temp>(Frame.esp,null),new List<Temp>(aux,new List<Temp>(Frame.esp,null))));
            if(regs==null){
                regs = new List<Temp>(aux, null);
            }
            else
            {
                regs.tail = new List<Temp>(regs.head, regs.tail);
                regs.head = aux;
            }
            arglist = arglist.tail;
        }
        //Emissao de call
        if(cALL.func instanceof NAME)
        {

            emit(new OPER("call " + ((NAME)cALL.func).label, Frame.calldefs, null));
        } else
        {
            Temp temp = munchExp(cALL.func);
            emit(new OPER("call `s0", Frame.calldefs, new List(temp, null)));
        }

        while(regs!= null){
            emit(new OPER("pop `d0",new List<Temp>(regs.head,new List<Temp>(Frame.esp,null)),new List<Temp>(Frame.esp,null)));
            regs = regs.tail;
        }
        return frame.RV();
    }

    private Temp munchTemp(TEMP e) {
        return e.temp;
    }

    private Temp munchName(NAME e) {
        Temp t = new Temp();
        emit(new OPER("mov `d0, " + e.label, new List(t, null), null));
        return t;
    }

    private Temp munchMem(MEM e) {
        Temp val = munchExp(e.exp);
        Temp add = new Temp();
        emit(new assem.OPER("mov `d0, [`s0]" ,new List<Temp>(add,null),new List<Temp>(val,null)));
        return add;
    }

    private Temp munchEseq(ESEQ eSEQ) {
        munchStm(eSEQ.stm);
        Temp dest = munchExp(eSEQ.exp);
        return dest;
    }

    private Temp munchConst(CONST e) {
        Temp t = new Temp();
        emit(new OPER("mov `d0,"+e.value,new List<Temp>(t,null), null));
        return t;
    }

    private Temp munchADD(BINOP bINOP){
         Temp val = new Temp();
         Temp add = new Temp();
         Temp ret = new Temp();
        if(bINOP.right instanceof MEM){
            val = munchExp(((MEM)bINOP.right).exp);
            add = munchExp(bINOP.left);
            emit(new assem.MOVE("mov `d0,`s0", ret, add));
            emit(new assem.OPER("add `d0,[`s1]" ,new List<Temp>(ret,null),new List<Temp>(ret,new List<Temp>(val,null))));
        }
        else if(bINOP.left instanceof MEM){
            add = munchExp(bINOP.right);
            val = munchExp(((MEM)bINOP.left).exp);
            emit(new assem.MOVE("mov `d0,`s0", ret, add));
            emit(new assem.OPER("add `d0,[`s1]" ,new List<Temp>(ret,null),new List<Temp>(ret,new List<Temp>(val,null))));
        }
        else if(bINOP.right instanceof CONST){
            add = munchExp(bINOP.left);
            emit(new assem.MOVE("mov `d0,`s0", ret, add));
            emit(new assem.OPER("add `d0,"+((CONST) bINOP.right).value,new List<Temp>(ret,null),new List<Temp>(ret,null)));
        }
        else if(bINOP.left instanceof CONST){
            add = munchExp(bINOP.right);
            emit(new assem.MOVE("mov `d0,`s0", ret, add));
            emit(new assem.OPER("add `d0,"+((CONST) bINOP.left).value,new List<Temp>(ret,null),new List<Temp>(ret,null)));
        }
        else{
            add = munchExp(bINOP.right);
            val = munchExp(bINOP.left);
            emit(new assem.MOVE("mov `d0,`s0", ret, add));
            emit(new assem.OPER("add `d0,`s1" ,new List<Temp>(ret,null),new List<Temp>(ret,new List<Temp>(val,null))));
        }
        return ret;
    }


    private Temp munchSUB(BINOP bINOP){
         Temp ret = new Temp();
         Temp val = new Temp();
         Temp add = new Temp();
         add = munchExp(bINOP.left);
         emit(new assem.MOVE("mov `d0,`s0", ret, add));
        if(bINOP.right instanceof MEM){
            val = munchExp(((MEM)bINOP.right).exp);
            emit(new assem.OPER("sub `d0,[`s1]" ,new List<Temp>(ret,null),new List<Temp>(ret,new List<Temp>(val,null))));
        }
        else if(bINOP.right instanceof CONST){
            emit(new assem.OPER("sub `d0,"+((CONST) bINOP.right).value,new List<Temp>(ret,null),new List<Temp>(ret,null)));
        }
        else{
            val = munchExp(bINOP.right);
            emit(new assem.OPER("sub `d0,`s0" ,new List<Temp>(ret,null),new List<Temp>(val,new List<Temp>(ret,null))));
        }
        return ret;
    }



    private Temp munchMUL(BINOP bINOP){
        Temp val = new Temp();
        Temp add = new Temp();
        Temp ret = new Temp();
        if(bINOP.right instanceof MEM){
            val = munchExp(((MEM)bINOP.right).exp);
            add = munchExp(bINOP.left);
            emit(new assem.MOVE("mov `d0, [`s0]" ,Frame.eax,val));
            emit(new assem.OPER("imul `s0" ,new List<Temp>(Frame.eax,new List<Temp>(Frame.edx,null)),new List<Temp>(add,new List<Temp>(Frame.eax,null))));
            emit(new assem.MOVE("mov `d0, `s0" ,ret,Frame.eax));
        }
        else if(bINOP.left instanceof MEM){
            add = munchExp(bINOP.right);
            val = munchExp(((MEM)bINOP.left).exp);
            emit(new assem.MOVE("mov `d0, [`s0]" ,Frame.eax,val));
            emit(new assem.OPER("imul `s0" ,new List<Temp>(Frame.eax,new List<Temp>(Frame.edx,null)),new List<Temp>(add,new List<Temp>(Frame.eax,null))));
            emit(new assem.MOVE("mov `d0, `s0" ,ret,Frame.eax));
        }
        else{
            add = munchExp(bINOP.right);
            val = munchExp(bINOP.left);
            emit(new assem.MOVE("mov `d0, `s0" ,Frame.eax,val));
            emit(new assem.OPER("imul `s0" ,new List<Temp>(Frame.eax,new List<Temp>(Frame.edx,null)),new List<Temp>(add,new List<Temp>(Frame.eax,null))));
            emit(new assem.MOVE("mov `d0, `s0" ,ret,Frame.eax));
        }
        return ret;
    }

    private Temp munchLSHIFT(BINOP bINOP) {
        Temp ls = new Temp();
        if(bINOP.left instanceof MEM){
            ls = munchExp(((MEM)bINOP.left).exp);
            emit(new assem.OPER("shl [`s0], " +((CONST)bINOP.right).value ,null,new List<Temp>(ls,null)));
        }
        else if(bINOP.left instanceof CONST)
        {
            ls = new Temp();
            emit(new assem.OPER("mov `d0, " +((CONST)bINOP.left).value ,new List<Temp>(ls,null),null));
            emit(new assem.OPER("shl `d0, " +((CONST)bINOP.right).value ,new List<Temp>(ls,null),null));
            return ls;
        }
        else{
            ls = munchExp(bINOP.left);
            emit(new assem.OPER("shl `d0, " +((CONST)bINOP.right).value ,new List<Temp>(ls,null),null));
        }
        return ls;
        
    }


}
